/*
 * Copyright (c) 2006-2021, Ulandlink Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-11-17     xpw          first version
 * 2023-02-20     xpw          sync
 */

#include <stdint.h>
#include <string.h>

#define LOG_TAG              "http.prs"
#define LOG_LVL               LOG_LVL_INFO
#include <at_log.h>

#include <http_pub.h>

#if HTTP_SERVER_ENABLE

#include <at_socket.h>
#include <http_parse.h>
#include <http_utils.h>
#include <http_upload.h>

#include <web_tool_api.h>

#define HTTP_CHAR_CR    '\r'
#define HTTP_CHAR_LF    '\n'

typedef struct http_result_code
{
    int result_code;
    char result_str[32];
} http_result_code_t;

enum http_haeder_end_flag
{
    HTTP_GET_NO_END = 0,
    HTTP_GET_ONE_CR,
    HTTP_GET_ONE_LF,
    HTTP_GET_TWO_CR,
    HTTP_GET_TWO_LF,
};

const struct http_method http_method_map[] = {
    {"GET ",    HTTP_METHOD_GET},
    {"POST ",   HTTP_METHOD_POST},
    {"",        HTTP_METHOD_UNKNOWN},
};

static const http_result_code_t http_result_code_map[] = {
     {0,        "Unknown"},
     {200,      "OK"},
     {400,      "Bad Request"},
     {401,      "Authorization Required"},
     {403,      "Forbidden"},
     {404,      "Not Found"},
     {405,      "Method Not Allowed"},
     {500,      "Internal Server Error"},
     {501,      "Not Implemented"},
     {505,      "HTTP Version Not Supported"},
};

rt_bool_t http_parse_header(char* http_header_buffer, http_request_t *request)
{
    char *ptr, *chr;
    static param_list_t _param_list;
    static param_t _param;
    static param_t _next_param;
    
    RT_ASSERT(http_header_buffer);
    RT_ASSERT(request);
    
    ptr = http_header_buffer;
    
    ptr += strlen(http_method_map[request->method - 1].method_str);
    
    /* get url */
    request->url = ptr;
    chr = strchr(ptr, ' ');
    if (chr == RT_NULL) {
        request->result_code = 400;
        LOG_E("parse http url failed.");
        return RT_ERROR;
    }
    
    *chr ++ = '\0';
    ptr = chr;
    
    //param_list_t *param_list = (param_list_t *)rt_malloc(sizeof(param_list_t));
    param_list_t *param_list = &_param_list;
    //LOG_W("\napply param_list\n");
    if (param_list == RT_NULL) {
        return RT_ERROR;
    }
    rt_memset(param_list, 0, sizeof(param_list_t));
    param_list->size = 0;

    /* if param followed */
    chr = strchr(request->url, '?');
    if (chr != RT_NULL) {
        *chr++ = '\0';
        while (*chr == ' ') chr ++;
        
        ptr = chr;
        
        //param_t *param = (param_t *)rt_malloc(sizeof(param_t));
        param_t *param = &_param;
    //LOG_W("\napply param\n");
        if (param == RT_NULL) {
            return RT_ERROR;
        }
        rt_memset(param, 0, sizeof(param_t));
        
        param_list->head = param;
        
        param_t *cur_param = param;
        
        while (1) {
            param_list->size ++;
            cur_param->key = ptr;
            chr = strchr(ptr, '=');
            if (chr != RT_NULL) {
                *chr ++ = '\0';
                ptr = chr;
                cur_param->value = ptr;
            }
            
            chr = ptr;
            
            while (*chr != '&' && *chr != '\0') chr ++;
            if (*chr == '&') {
//                param_t *next_param = (param_t *)rt_malloc(sizeof(param_t));
                param_t *next_param = &_next_param;
//    LOG_W("\napply next_param\n");
                if (next_param == RT_NULL) {
                    return RT_ERROR;
                }
               
                rt_memset(next_param, 0, sizeof(param_t));
                cur_param->next = next_param;
                cur_param = next_param;
                *chr ++ = '\0';
                ptr = chr;
                continue;
            }
            
            *chr ++ = '\0';
            ptr = chr;
            break;
         }
    }
    request->param_list = param_list;
    
    /* get http protocol */
    if (web_tool_str_begin_with(ptr, "HTTP/1.0")) {
        request->protocol = HTTP_PROTOCOL_0;
    } else if (web_tool_str_begin_with(ptr, "HTTP/1.1")) {
        request->protocol = HTTP_PROTOCOL_1;
    } else {
        request->protocol = HTTP_PROTOCOL_UNKNOWN;
        request->result_code = 501;
        LOG_E("parse http protocol failed.");
        return RT_ERROR;
    }

    chr = strstr(ptr, "\r\n");
    if (chr == RT_NULL) {
        request->result_code = 400;
        LOG_E("7");
        return RT_ERROR;
    }
    *chr ++ = '\0';
    *chr ++ = '\0';
    ptr = chr;
    
    while (1) {
        /* end of request header */
        if (web_tool_str_begin_with(ptr, "\r\n")) {            
            /* check whether it's an uploading request */
            if (web_tool_str_begin_with(request->content_type, "multipart/form-data"))
            {
                
            }
            /* end of http request */
            request->result_code = 200;

            return RT_EOK;
        }

        /* end of http request */
        if (*ptr == '\0') {
            request->result_code = 200;
            
            return RT_EOK;
        }
        
        if (web_tool_str_begin_with(ptr, "Host:")) {
            ptr += 5;
            while (*ptr == ' ') ptr ++;
            request->host = ptr;
        } else if (web_tool_str_begin_with(ptr, "User-Agent:")) {
            ptr += 11;
            while (*ptr == ' ') ptr ++;
            request->user_agent = ptr;
        } else if (web_tool_str_begin_with(ptr, "Accept-Language:")) {
            ptr += 16;
            while (*ptr == ' ') ptr ++;
            request->accept_language = ptr;
        }else if (web_tool_str_begin_with(ptr, "Accept:")) {
            ptr += 7;
            while (*ptr == ' ') ptr ++;
            request->accept = ptr;
        } else if (web_tool_str_begin_with(ptr, "Content-Length:")) {
            ptr += 15;
            while (*ptr == ' ') ptr ++;
            request->content_length = atoi(ptr);
        } else if (web_tool_str_begin_with(ptr, "Content-Type:")) {
            ptr += 13;
            while (*ptr == ' ') ptr ++;
            request->content_type = ptr;
        } else if (web_tool_str_begin_with(ptr, "Referer:")) {
            ptr += 8;
            while (*ptr == ' ') ptr ++;
            request->referer = ptr;
        } else if (web_tool_str_begin_with(ptr, "Connection:")) {
            ptr += 11;
            while (*ptr == ' ') ptr ++;

            if (web_tool_str_begin_with(ptr, "close")) {
                request->connection = HTTP_CONN_CLOSED;
            } else if (web_tool_str_begin_with(ptr, "Keep-Alive")) {
                request->connection = HTTP_CONN_ALIVE;
            } else {
                request->connection = HTTP_CONN_UNKNOWN;
            }
        } else if (web_tool_str_begin_with(ptr, "Cookie:")) {
            ptr += 7;
            while (*ptr == ' ') ptr ++;
            request->cookie = ptr;
        } else if (web_tool_str_begin_with(ptr, "Accept-Encoding:")) {
            const char *end = strstr(ptr, "\r\n");
            const char *gzip = strstr(ptr, "gzip");

            ptr += 16;
            while (*ptr == ' ') ptr ++;
            request->accept_encoding = ptr;
            
            if((gzip != RT_NULL) && (end != RT_NULL) && (gzip < end))
            {
                request->support_gzip = RT_TRUE;
            }
        }

        chr = strstr(ptr, "\r\n");
        if (chr == RT_NULL)
        {
            request->result_code = 400;
            LOG_W("http request is incomplete, ends with [%s]", ptr);
            return RT_ERROR;
        }
        *chr ++ = '\0';
        *chr ++ = '\0';

        ptr = chr;
    }
}

rt_bool_t http_recv_request(int socket, http_request_t *request)
{
#define HTTP_TRY_GET_LENGTH         300
#define HTTP_REQUEST_HEADER_LEN     1024
#define HTTP_GET_HEADER_TIMEOUT     (2 * RT_TICK_PER_SECOND)
    
    RT_ASSERT(request);
    
    int ret, length;
    rt_tick_t start_time;
    char *ptr, *content_buffer;
    char chr[1] = {0};
    enum http_haeder_end_flag state = HTTP_GET_NO_END;
    static char request_buffer[HTTP_REQUEST_HEADER_LEN];
    
    rt_memset(request_buffer, 0, sizeof(request_buffer));
    ret = at_recv(socket, request_buffer, HTTP_TRY_GET_LENGTH, MSG_DONTWAIT);
    if (ret <= 0) {
        return RT_FALSE;
    }
    
    /* find http method */
    for (int i = 0; i < HTTP_METHOD_MAX; i ++) {
        ptr = rt_strstr(request_buffer, http_method_map[i].method_str);
        if (ptr != RT_NULL) {
            request->method = http_method_map[i].http_method_e;
            break;
        }
    }
    
    if (request->method == HTTP_METHOD_UNKNOWN) {
        //rt_kprintf("discard %d datas\r\n", ret);
        return RT_FALSE;
    }
    
    start_time = rt_tick_get();
    
    while (1) {
        /* Analysis forcely when get /r/n/r/n timeout */
        if (rt_tick_get() - start_time > HTTP_GET_HEADER_TIMEOUT) {
            goto __parse;
        }
        chr[0] = '\0';
        ret = at_recv(socket, chr, 1, MSG_DONTWAIT);
        if (ret <= 0) {
            rt_thread_mdelay(50);
            continue;
        }
        strncat(request_buffer, chr, 1);
        
        switch (state) {
            
            case HTTP_GET_NO_END:
                if (chr[0] == HTTP_CHAR_CR) {
                    state = HTTP_GET_ONE_CR;
                }
                break;
            
            case HTTP_GET_ONE_CR:
                if (chr[0] == HTTP_CHAR_LF) {
                    state = HTTP_GET_ONE_LF;
                } else {
                    state = HTTP_GET_NO_END;
                }
                break;
            
            case HTTP_GET_ONE_LF:
                if (chr[0] == HTTP_CHAR_CR) {
                    state = HTTP_GET_TWO_CR;
                } else {
                    state = HTTP_GET_NO_END;
                }
                break;
            
            case HTTP_GET_TWO_CR:
                if (chr[0] == HTTP_CHAR_LF) {
                    state = HTTP_GET_TWO_LF;
                } else {
                    state = HTTP_GET_NO_END;
                }
                break;
            
            case HTTP_GET_TWO_LF:
                break;
        }
        
        /* get "\r\n\r\n" */
        if (state == HTTP_GET_TWO_LF) {
__parse:
            http_parse_header(ptr, request);
            /* do not accept when it is multipart/form-data */
            if (request->content_length > 0 && request->result_code == 200
               && !rt_strstr(request->content_type, "multipart/form-data")) {
                content_buffer = (char *)rt_malloc(request->content_length);;
                ret = at_recv(socket, content_buffer, request->content_length, MSG_DONTWAIT);
                request->content_buffer = content_buffer;
            }
            return RT_TRUE;
        }
        start_time = rt_tick_get();
    }
}

void http_resp_request(int socket, http_request_t *request)
{
    char *ptr;
    int fd, ret, user_status;
    char file_buffer[HTTP_FILE_CHUNK_BUFFER] = {0};
    char send_url_gzip[HTTP_REQUEST_URL_MAXLEN] = {0};
    char send_url[HTTP_REQUEST_URL_MAXLEN] = WEB_ROOT;
    
    /* upload */
    if (rt_strstr(request->content_type, "multipart/form-data")) {
        http_resp_upload_request(socket, request);
        return;
    } else {
        if (request->result_code == 200) {
            /* get url */
            if (!rt_strcmp(request->url, "/")) {
                strcat(send_url, "/index.html");
            } else {
                strcat(send_url, request->url);
            }
    
            /* cgi-bin func callback */
            ptr = rt_strstr(request->url,"/cgi-bin/");
            if (ptr != RT_NULL) {
                ptr += 9;
                if (http_run_cgi_by_name(socket, ptr, request)) {
                    return;
                }
            }
            
            /* get url request */
            rt_memcpy(send_url_gzip, send_url, sizeof(send_url_gzip));
            strcat(send_url_gzip, ".gz");
            /* find gzip */
            if (access(send_url_gzip, 0) == 0) {
                fd = open(send_url_gzip, O_RDONLY, 0);
                if (fd < 0) {
                    LOG_W("web server open gzip url (%s) failed.", send_url);
                    request->result_code = 500;
                } else {
                    strcat(send_url, ".gz");
                }
            } else if (access(send_url, 0) == 0) {
                fd = open(send_url, O_RDONLY, 0);
                if (fd < 0) {
                    LOG_W("web server open url (%s) failed.", send_url);
                    request->result_code = 500;
                } 
            } else {
                request->result_code = 404;
                if (access(WEB_DEFAULT_FILE, 0) == 0) {
                    rt_memset(send_url, 0, HTTP_REQUEST_URL_MAXLEN);
                    rt_memcpy(send_url, WEB_DEFAULT_FILE, strlen(WEB_DEFAULT_FILE));
                    fd = open(send_url, O_RDONLY, 0);
                    if (fd < 0) {
                        LOG_W("web server open url (%s) failed.", send_url);
                        request->result_code = 500;
                    } else {
                        request->result_code = 200;
                    }
                }
            }
        }
        
        /* check user id */
        if (rt_strcmp(send_url, WEB_DEFAULT_FILE)) {
            user_status = web_tool_check_user_id(request->cookie);
            if (user_status != WEB_USER_AVAILABLE) {
                http_send_cgi_resp(socket, (char *)_not_verified_or_expired, strlen(_not_verified_or_expired));
                if (fd >= 0) {
                    close(fd);
                }
                return;
            }
        }
        
        http_send_url_header(socket, request, send_url);
        
        if (request->result_code == 200 && fd >= 0) {
            while (1) {
                ret = read(fd, file_buffer, sizeof(file_buffer));
                if (ret > 0) {
                    at_send(socket, file_buffer, ret, MSG_WAITALL);
                } else {
                    close(fd);
                    break;
                }
            }
            LOG_D("http resp body url(%s) is sent.", send_url);
        }
    }
}

char *http_get_result_str(int result_code)
{
    for (int i = 0; i < (sizeof(http_result_code_map)/sizeof(http_result_code_t)); i ++) {
        if (http_result_code_map[i].result_code == result_code) {
            return (char *)http_result_code_map[i].result_str;
        }
    }
    
    return (char *)http_result_code_map[0].result_str;
}

void http_send_url_header(int socket, http_request_t *request, char *send_url)
{
    static const char *not_found = "404 Not Found";
    static const char *fmt = "HTTP/1.1 %d %s\r\nServer: %s\r\n";
    static const char *content = "Content-Type: %s\r\nContent-Length: %ld\r\n";
    static const char *connect = "Connection: %s\r\n";
    static const char *encoding = "Content-Encoding: gzip\r\n";
    static const char *cache = "Cache-control: max-age=%d\r\n";
    static const char *auth = "WWW-Authenticate: Basic realm=%s\r\n";
    static const char *acao = "Access-Control-Allow-Origin:*\r\n\r\n";
    
    struct stat t_stat;
    char *ptr, *end_buffer;
    char header_buffer[512] = {0};
    int offset, length, resp_content_len = 0;
    
    if (request->result_code == 200) {
        /* find the file which name is url */
        if (stat(send_url, &t_stat) == 0) {
            resp_content_len = t_stat.st_size;
        } else {
            LOG_E("server cannot find url: %s", send_url);
        }
    }
    
    ptr = header_buffer;
    end_buffer = header_buffer + sizeof(header_buffer);
    
    /* fmt */
    offset = rt_snprintf(ptr, end_buffer - ptr, fmt, request->result_code, http_get_result_str(request->result_code), HTTP_SERVER_NAME);
    ptr += offset;

    /* use WWW-Authenticate if result coed is 401 */
    if (request->result_code == 401) {
        offset = rt_snprintf(ptr, end_buffer - ptr, auth, request->host);
        ptr += offset;
    }
    
    if (request->result_code == 200) {
        /* content */
        offset = rt_snprintf(ptr, end_buffer - ptr, content,  http_mime_get_type(send_url), resp_content_len);                      
        ptr += offset;
        
        /* cache */
        offset = rt_snprintf(ptr, end_buffer - ptr, cache, HTTP_CACHE_CONTROL);                      
        ptr += offset;

        /* connection */
        offset = rt_snprintf(ptr, end_buffer - ptr, connect, request->connection == HTTP_CONN_CLOSED? "close" : "Keep-Alive");
        //offset = rt_snprintf(ptr, end_buffer - ptr, connect, "close");
        ptr += offset;
        
        if (strstr(send_url, ".gz")) {
            /* encoding */
            offset = rt_snprintf(ptr, end_buffer - ptr, encoding);
            ptr += offset;
        }
    } else if (request->result_code == 404) {
        /* content */
        offset = rt_snprintf(ptr, end_buffer - ptr, content,  "text/plain", strlen(not_found));                      
        ptr += offset;

        /* connection */
        offset = rt_snprintf(ptr, end_buffer - ptr, connect, "close");
        
        ptr += offset;
    } else {
        /* connection */
        offset = rt_snprintf(ptr, end_buffer - ptr, connect, "close");
        ptr += offset;
    }
    
    /* acao */
    offset = rt_snprintf(ptr, end_buffer - ptr, acao);
    ptr += offset;
    
    /* get the total length */
    length = ptr - header_buffer;
    
    at_send(socket, header_buffer, length, MSG_WAITALL);
    
    if (request->result_code == 404) {
        at_send(socket, not_found, strlen(not_found), MSG_WAITALL);
    }
}

void http_request_destroy(http_request_t **request)
{
    param_t *cur_param, *next_param;
    int size = (*request)->param_list->size;

    if (size > 0) {
        cur_param = (*request)->param_list->head;
        for (int i = 0; i < size - 1; i ++) {
            next_param = cur_param->next;
            rt_free(cur_param);
            cur_param = RT_NULL;
            cur_param = next_param;
        }
        rt_free(cur_param);
        cur_param = RT_NULL;
    }
    
    rt_free((*request)->param_list);
    (*request)->param_list= RT_NULL;
    rt_free(*request);
    *request = RT_NULL;
}

void http_content_destroy(http_request_t **request)
{
    if ((*request)->content_length > 0) {
        rt_free((*request)->content_buffer);
    }
}

void http_print_request(int socket, http_request_t *request)
{
    rt_kprintf("\n---------- HTTP REQUEST FROM SKT %d -----------\n", socket);
    rt_kprintf("method:%d\n", request->method);
    rt_kprintf("url:%s\n", request->url);
    rt_kprintf("protocol:%d\n", request->protocol);
    rt_kprintf("host:%s\n", request->host);
    rt_kprintf("user_agent:%s\n", request->user_agent);
    rt_kprintf("accept:%s\n", request->accept);
    rt_kprintf("accept_encoding:%s\n", request->accept_encoding);
    rt_kprintf("accept_language:%s\n", request->accept_language);
    rt_kprintf("content_length:%d\n", request->content_length);
    rt_kprintf("connection:%d\n", request->connection);
    rt_kprintf("content_type:%s\n", request->content_type);
    rt_kprintf("result_code:%c\n", request->result_code);
    rt_kprintf("content_buffer:\n");
    for (int i = 0; i < request->content_length; i ++) {
        rt_kprintf("%c", *(request->content_buffer + i));
    }
    
    if (request->param_list->size > 0) {
        param_t *ptr = request->param_list->head;

        rt_kprintf("\n");
        for(int i = 0; i < request->param_list->size; i ++) {
            rt_kprintf("key[%d]:%s ", i, ptr->key);
            rt_kprintf("value[%d]:%s\n", i, ptr->value);
            
            if (ptr->next != RT_NULL) {
                ptr = ptr -> next;
            } else {
                break;
            }
        }
    }
    
    rt_kprintf("-------------- HTTP REQUEST END --------------\n");
}

#endif
